1<script lang="ts">
2 import { goto, replaceState } from '$app/navigation';
3 import { page } from '$app/state';
4 import { afterNavigate } from '$app/navigation';
5 import { ChatScreen, DialogModelNotAvailable } from '$lib/components/app';
6 import { chatStore, isLoading } from '$lib/stores/chat.svelte';
7 import {
8 conversationsStore,
9 activeConversation,
10 activeMessages
11 } from '$lib/stores/conversations.svelte';
12 import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
13
14 let chatId = $derived(page.params.id);
15 let currentChatId: string | undefined = undefined;
16
17 // URL parameters for prompt and model selection
18 let qParam = $derived(page.url.searchParams.get('q'));
19 let modelParam = $derived(page.url.searchParams.get('model'));
20
21 // Dialog state for model not available error
22 let showModelNotAvailable = $state(false);
23 let requestedModelName = $state('');
24 let availableModelNames = $derived(modelOptions().map((m) => m.model));
25
26 // Track if URL params have been processed for this chat
27 let urlParamsProcessed = $state(false);
28
29 /**
30 * Clear URL params after message is sent to prevent re-sending on refresh
31 */
32 function clearUrlParams() {
33 const url = new URL(page.url);
34 url.searchParams.delete('q');
35 url.searchParams.delete('model');
36 replaceState(url.toString(), {});
37 }
38
39 async function handleUrlParams() {
40 // Ensure models are loaded first
41 await modelsStore.fetch();
42
43 // Handle model parameter - select model if provided
44 if (modelParam) {
45 const model = modelsStore.findModelByName(modelParam);
46 if (model) {
47 try {
48 await modelsStore.selectModelById(model.id);
49 } catch (error) {
50 console.error('Failed to select model:', error);
51 requestedModelName = modelParam;
52 showModelNotAvailable = true;
53 return;
54 }
55 } else {
56 // Model not found - show error dialog
57 requestedModelName = modelParam;
58 showModelNotAvailable = true;
59 return;
60 }
61 }
62
63 // Handle ?q= parameter - send message in current conversation
64 if (qParam !== null) {
65 await chatStore.sendMessage(qParam);
66 // Clear URL params after message is sent
67 clearUrlParams();
68 } else if (modelParam) {
69 // Clear params even if no message was sent (just model selection)
70 clearUrlParams();
71 }
72
73 urlParamsProcessed = true;
74 }
75
76 async function selectModelFromLastAssistantResponse() {
77 const messages = activeMessages();
78 if (messages.length === 0) return;
79
80 let lastMessageWithModel: DatabaseMessage | undefined;
81
82 for (let i = messages.length - 1; i >= 0; i--) {
83 if (messages[i].model) {
84 lastMessageWithModel = messages[i];
85 break;
86 }
87 }
88
89 if (!lastMessageWithModel) return;
90
91 const currentModelId = selectedModelId();
92 const currentModelName = modelOptions().find((m) => m.id === currentModelId)?.model;
93
94 if (currentModelName === lastMessageWithModel.model) {
95 return;
96 }
97
98 const matchingModel = modelOptions().find(
99 (option) => option.model === lastMessageWithModel.model
100 );
101
102 if (matchingModel) {
103 try {
104 await modelsStore.selectModelById(matchingModel.id);
105 console.log(`Automatically loaded model: ${lastMessageWithModel.model} from last message`);
106 } catch (error) {
107 console.warn('Failed to automatically select model from last message:', error);
108 }
109 }
110 }
111
112 afterNavigate(() => {
113 setTimeout(() => {
114 selectModelFromLastAssistantResponse();
115 }, 100);
116 });
117
118 $effect(() => {
119 if (chatId && chatId !== currentChatId) {
120 currentChatId = chatId;
121 urlParamsProcessed = false; // Reset for new chat
122
123 // Skip loading if this conversation is already active (e.g., just created)
124 if (activeConversation()?.id === chatId) {
125 // Still handle URL params even if conversation is active
126 if ((qParam !== null || modelParam !== null) && !urlParamsProcessed) {
127 handleUrlParams();
128 }
129 return;
130 }
131
132 (async () => {
133 const success = await conversationsStore.loadConversation(chatId);
134 if (success) {
135 chatStore.syncLoadingStateForChat(chatId);
136
137 // Handle URL params after conversation is loaded
138 if ((qParam !== null || modelParam !== null) && !urlParamsProcessed) {
139 await handleUrlParams();
140 }
141 } else {
142 await goto('#/');
143 }
144 })();
145 }
146 });
147
148 $effect(() => {
149 if (typeof window !== 'undefined') {
150 const handleBeforeUnload = () => {
151 if (isLoading()) {
152 console.log('Page unload detected while streaming - aborting stream');
153 chatStore.stopGeneration();
154 }
155 };
156
157 window.addEventListener('beforeunload', handleBeforeUnload);
158
159 return () => {
160 window.removeEventListener('beforeunload', handleBeforeUnload);
161 };
162 }
163 });
164</script>
165
166<svelte:head>
167 <title>{activeConversation()?.name || 'Chat'} - llama.cpp</title>
168</svelte:head>
169
170<ChatScreen />
171
172<DialogModelNotAvailable
173 bind:open={showModelNotAvailable}
174 modelName={requestedModelName}
175 availableModels={availableModelNames}
176/>